(function () {
    // 这个就是 React Element 的树，也就是我们常说的 vdom
    function createElement(type, props, ...children) {
        return {
            type,
            props: {
                ...props,
                children: children.map((child) => {
                    const isTextNode = typeof child === 'string' || typeof child === 'number'
                    return isTextNode ? createTextNode(child) : child
                })
            }
        }
    }

    function createTextNode(nodeValue) {
        return {
            type: "TEXT_ELEMENT",
            props: {
                nodeValue,
                children: []
            },
        };
    }

    let nextUnitOfWork = null  // 用 nextUnitOfWork 指向下一个要处理的 fiber 节点
    let wipRoot = null // 当前正在处理的 fiber 链表的根 wipRoot
    let currentRoot = null // 之前的历史 fiber 链表的根 currentRoot。
    // 为什么有两个 root 呢？
    // 因为初始渲染会生成一个 fiber 链表，然后后面 setState 更新会再生成一个新的 fiber 链表，两个 fiber 链表要做一些对比里决定对 dom 节点的增删改，所以都要保存
    let deletions = null

    function render(element, container) {
        wipRoot = {
            dom: container,
            props: {
                children: [element]
            },
            alternate: currentRoot
        }
        deletions = []
        nextUnitOfWork = wipRoot
    }

    // 我们用 reqeustIdleCallback 来代替 React 的时间分片，把 React Element 树转 fiber 的 reconcile 过程放到不同的任务里跑。
    // 每次跑的时候判断下 timeRemaing 是否接近 0，是的话就中断循环，等下次 requestIdleCallback 的回调再继续处理 nextUnitOfWork 指向的 fiber 节点。
    function workLoop(deadline) {
        let shouldYield = false
        while (nextUnitOfWork && !shouldYield) {
            nextUnitOfWork = performUnitWork(nextUnitOfWork)
            shouldYield = deadline.timeRemaining() < 1
        }
        // 只要在 reconcile 结束，也就是没有 nextUnitOfWork 的时候执行 commit 就行了
        if (!nextUnitOfWork && wipRoot) {
            commitRoot()
        }
        requestIdleCallback(workLoop)
    }
    requestIdleCallback(workLoop)

    // performUnitOfWork 处理每个 fiber 节点之后，会按照 child、sibling、return 的顺序返回下一个要处理的 fiber 节点
    // 处理每个 fiber 节点的时候，要根据类型做不同的处理
    function performUnitWork(fiber) {
        // 判断下是函数组件（FunctionComponent），还是原生标签（HostComponent），分别做处理
        const isFunctionComponent = fiber.type instanceof Function
        if (isFunctionComponent) {
            updateFunctionComponent(fiber)
        } else {
            updateHostComponent(fiber)
        }
        if (fiber.child) {
            return fiber.child
        }
        let nextFiber = fiber
        while (nextFiber) {
            if (nextFiber.sibling) {
                return nextFiber.sibling
            }
            nextFiber = nextFiber.return
        }
    }

    // wipFiber 指向当前处理的 fiber（之前的 nextUnitOfWork 是指向下一个要处理的 fiber 节点）
    //  stateHooks 数组来存储 useState 的 hook 的值，用 effectHooks 数组存储 useEffect 的 hook 的值
    let wipFiber = null
    let stateHookIndex = null

    // 函数组件就是传入 props 调用它，并且函数组件的返回值就是要继续 reconcile 的节点
    function updateFunctionComponent(fiber) {
        wipFiber = fiber
        stateHookIndex = 0
        wipFiber.stateHooks = []
        wipFiber.effectHooks = []

        const children = [fiber.type(fiber.props)]
        reconcileChildren(fiber, children)
    }
    function updateHostComponent(fiber) {
        if (!fiber.dom) {
            fiber.dom = createDom(fiber)
        }
        reconcileChildren(fiber, fiber.props.children)
    }

    function createDom(fiber) {
        const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode('') : document.createElement(fiber.type)
        updateDom(dom, {}, fiber.props)
        return dom
    }

    const isEvent = key => key.startsWith("on")
    const isProperty = key => key !== "children" && !isEvent(key)
    const isNew = (prev, next) => key => prev[key] !== next[key]
    const isGone = (prev, next) => key => !(key in next)

    // 更新 props。首先删除旧的事件监听器，旧的属性，然后添加新的属性、新的事件监听器。
    function updateDom(dom, prevProps, nextProps) {
        //Remove old or changed event listeners
        Object.keys(prevProps)
            .filter(isEvent)
            .filter(
                key => !(key in nextProps) || isNew(prevProps, nextProps)(key)
            )
            .forEach(name => {
                const eventType = name.toLowerCase().substring(2)
                dom.removeEventListener(eventType, prevProps[name])
            })
        // Remove old properties
        Object.keys(prevProps)
            .filter(isProperty)
            .filter(isGone(prevProps, nextProps))
            .forEach(name => {
                dom[name] = ""
            })
        // Set new or changed properties
        Object.keys(nextProps)
            .filter(isProperty)
            .filter(isNew(prevProps, nextProps))
            .forEach(name => {
                dom[name] = nextProps[name]
            })

        // Add event listeners
        Object.keys(nextProps)
            .filter(isEvent)
            .filter(isNew(prevProps, nextProps))
            .forEach(name => {
                const eventType = name.toLowerCase().substring(2)
                dom.addEventListener(eventType, nextProps[name])
            })

    }

    // 处理它们的子节点
    function reconcileChildren(wipFiber, elements) {
        let index = 0
        let oldFiber = wipFiber.alternate?.child
        let prevSibling = null
        // 拿到 alternate 的 child，依次取 sibling，逐一和新的 fiber 节点对比
        while (index < elements.length || oldFiber != null) {
            const element = elements[index]
            let newFiber = null
            const sameType = element?.type == oldFiber?.type
            // alternate 是对应的旧的 fiber 节点。
            // effectTag 是增删改的标记。
            if (sameType) {
                newFiber = {
                    type: oldFiber.type,
                    props: element.props,
                    dom: oldFiber.dom,
                    return: wipFiber,
                    alternate: oldFiber,
                    effectTag: "UPDATE",
                }
            }
            if (element && !sameType) {
                newFiber = {
                    type: element.type,
                    props: element.props,
                    dom: null,
                    return: wipFiber,
                    alternate: null,
                    effectTag: "PLACEMENT",
                }
            }
            if (oldFiber && !sameType) {
                oldFiber.effectTag = "DELETION"
                deletions.push(oldFiber)
            }
            if (oldFiber) {
                oldFiber = oldFiber.sibling
            }
            if (index === 0) {
                wipFiber.child = newFiber
            } else if (element) {
                prevSibling.sibling = newFiber
            }
            prevSibling = newFiber
            index++
        }
    }

    // 每次调用 useState 时会在 stateHooks 添加一个元素来保存 state
    function useState(initialState) {
        const currentFiber = wipFiber
        const oldHook = wipFiber.alternate?.stateHooks[stateHookIndex]

        const stateHook = {
            state: oldHook ? oldHook.state : initialState,
            queue: oldHook ? oldHook.queue : []
        }

        // 对初始 state 执行多个 action（也就是 setState） 之后，就拿到了最终的 state 值
        stateHook.queue.forEach((action) => {
            stateHook.state = action(stateHook.state)
        })
        stateHook.queue = []
        stateHookIndex++
        wipFiber.stateHooks.push(stateHook)

        // setState 就是在 action 数组里添加新的 action，并且让 nextUnitOfWork 指向新的 wipRoot，从而开始新的一轮渲染：
        function setState(action) {
            const isFunction = typeof action === 'function'

            stateHook.queue.push(isFunction ? action : () => action)
            wipRoot = {
                ...currentFiber,
                alternate: currentFiber
            }
            nextUnitOfWork = wipRoot
        }
        return [stateHook.state, setState]
    }

    // 就是在 fiber.effectHooks 上添加一个元素, 等 reconcile 结束，fiber 链表就构建好了，在 fiber 上打上了增删改的标记，并且也保存了要执行的 effect。这个阶段是 commit
    // 接下来只要遍历这个构建好的 fiber 链表，执行增删改和 effect 函数就好了
    function useEffect(callback, deps) {
        const effectHook = {
            callback,
            deps,
            cleanup: undefined
        }
        wipFiber.effectHooks.push(effectHook)
    }

    // 在 commitRoot 里，我们先把需要删除的节点都删掉，然后遍历 fiber 链表，处理其它节点
    function commitRoot() {
        deletions.forEach(commitWork)
        commitWork(wipRoot.child)
        commitEffectHooks()
        currentRoot = wipRoot
        wipRoot = null
        deletions = []
    }

    // commitWork 按照 child、sibling 的顺序来递归遍历 fiber 链表
    function commitWork(fiber) {
        if (!fiber) {
            return
        }
        let domParentFiber = fiber.return
        while (!domParentFiber.dom) {
            domParentFiber = domParentFiber.return
        }

        const domParent = domParentFiber.dom

        if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
            domParent.appendChild(fiber.dom)
        } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
            updateDom(fiber.dom, fiber.alternate.props, fiber.props)
        } else if (fiber.effectTag === "DELETION") {
            commitDeletion(fiber, domParent)
        }
        commitWork(fiber.child)
        commitWork(fiber.sibling)
    }

    // 删除的时候，如果当前 fiber 节点没有对应的 dom，就不断 child 向下找
    function commitDeletion(fiber, domParent) {
        if (fiber.dom) {
            domParent.removeChild(fiber.dom)
        } else {
            commitDeletion(fiber.child, domParent)
        }
    }

    function commitEffectHooks() {
        function runCleanup(fiber) {
            if (!fiber) {
                return
            }
            fiber.alternate?.effectHooks?.forEach((hook, index) => {
                const deps = fiber.effectHooks[index].deps
                if (!hook.deps || !isDepsEqual(hook.deps, deps)) {
                    hook.cleanup?.()
                }
            })

            runCleanup(fiber.child)
            runCleanup(fiber.sibling)
        }

        function run(fiber) {
            if (!fiber) {
                return
            }
            fiber.effectHooks?.forEach((newHook, index) => {
                // 当没有 alternate 的时候，就是首次渲染，直接执行所有的 effect
                if (!fiber.alternate) {
                    newHook.cleanup = newHook.callback()
                    return
                }
                // 如果没传入 deps 或者 deps 数组变化的时候再执行 effect 函数
                if (!newHook.deps) {
                    newHook.cleanup = newHook.callback();
                }
                if (newHook.deps.length > 0) {
                    const oldHook = fiber.alternate?.effectHooks[index];
                    // 当没有传入 deps 数组，或者 deps 数组和上次不一致时，就执行 cleanup 函数。
                    if (!isDepsEqual(oldHook.deps, newHook.deps)) {
                        newHook.cleanup = newHook.callback()
                    }
                }
            })
            run(fiber.child)
            run(fiber.sibling)
        }
        runCleanup(wipRoot)
        run(wipRoot)
    }

    function isDepsEqual(deps, newDeps) {
        if (deps.length !== newDeps.length) {
            return false;
        }

        for (let i = 0; i < deps.length; i++) {
            if (deps[i] !== newDeps[i]) {
                return false;
            }
        }
        return true;
    }

    const MiniReact = {
        createElement,
        render,
        useState,
        useEffect
    }

    window.MiniReact = MiniReact
})()